# shellcheck shell=bash
COLOUR_BLUE=$(tput setaf 4 :-"" 2>/dev/null)
COLOUR_RED=$(tput setaf 1 :-"" 2>/dev/null)
COLOUR_RESET=$(tput sgr0 :-"" 2>/dev/null)

BOLD=$(tput bold)
NORMAL=$(tput sgr0)

PROGRESS_SERVER='127.0.0.1:51234'

main() {
  enable_programmable_completion
  set_prompt_command
  source_aliases

  motd
  welcome

  unset -f main enable_programmable_completion set_prompt_command source_aliases
}

source_aliases() {
  if [[ -f ~/.bash_aliases ]]; then
    # shellcheck source=/dev/null
    source ~/.bash_aliases
  fi
}

enable_programmable_completion() {
  # enable programmable completion features
  if ! shopt -oq posix; then
    if [ -f /usr/share/bash-completion/bash_completion ]; then
      # shellcheck disable=SC1091
      . /usr/share/bash-completion/bash_completion
    elif [ -f /etc/bash_completion ]; then
      # shellcheck disable=SC1091
      . /etc/bash_completion
    fi
  fi
}

set_prompt_command() {
  PROMPT_COMMAND=__prompt_command
}

__prompt_command() {
  local PREVIOUS_COMMAND_STATUS_CODE="$?"
  local STATUS_CODE_COLOR="${COLOUR_RESET}"

  PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]'

  if [[ "${PREVIOUS_COMMAND_STATUS_CODE}" != 0 ]]; then
    STATUS_CODE_COLOR=${COLOUR_RED}
  fi

  PS1="${PS1}[${STATUS_CODE_COLOR}${PREVIOUS_COMMAND_STATUS_CODE}${COLOUR_RESET}]\$ "
}

#
# Logging
#
warning() {
  [ "${*:-}" ] && ERROR="$*" || ERROR="Unknown Warning"
  printf "%s\\n" "${COLOUR_RED}${ERROR}${COLOUR_RESET}"
} 1>&2
readonly -f warning

info() {
  [ "${*:-}" ] && INFO="$*" || INFO="Unknown Info"
  printf "%s\\n" "${COLOUR_BLUE}${INFO}${COLOUR_RESET}"
} 1>&2
readonly -f info

#
# Starting point.
#

check_tasks_yaml_exists() {
  if ! yq -j r /tasks.yaml | jq -r '.name' &>/dev/null ; then
    warning "No scenario appears to have been launched. Please launch one with 'simulator scenario launch ...'."
    return 1
  fi
}
readonly -f check_tasks_yaml_exists

find_current_task() {
  local current_task scenario_id

  if ! check_tasks_yaml_exists; then
    return 1
  fi

  scenario_id=$(yq -j r /tasks.yaml | jq -r '.name')
  current_task=$(curl -s "${PROGRESS_SERVER}"?scenario="${scenario_id}" | jq -r '.currentTask')

  if [[ ${current_task} != "null" ]]; then
    echo "${current_task}"
    return
  else
    warning "No task found! Please start a scenario task with 'start_task ...'."
    return 1
  fi
}
readonly -f find_current_task

check_if_task_set() {
  local task scenario_id

  if ! check_tasks_yaml_exists; then
    return 1
  fi

  scenario_id=$(yq -j r /tasks.yaml | jq -r '.name')
  task=$(curl -s "${PROGRESS_SERVER}"?scenario="${scenario_id}" | jq -r '.currentTask')

  if [[ ${task} = "null" ]]; then
    echo "You have not started a task yet. One can be set with 'start_task ...'. Would you like to continue anyway without starting a task? Enter '1' or '2'"
    select yesno in "Yes" "No"; do
        case $yesno in
            Yes ) return 0;;
            No ) return 1;;
        esac
    done
  fi
}
readonly -f check_if_task_set

starting_point() {
  local task_no
  local task_json
  local regex
  local MODE
  local KUBECTL_ACCESS
  local NODE_TYPE
  local NODE_NUMBER

  if ! check_if_task_set; then
    return 1
  fi

  task_no=$(find_current_task)
  task_json=$(yq r -j /tasks.yaml)
  #test that the task number has been found correctly
  regex='^[0-9]+$'
  if ! [[ ${task_no} =~ ${regex} ]]; then
    warning "Task number not found correctly"
    return 1
  fi
  # Identify the starting point mode
  MODE="$(echo "${task_json}" | jq -r --arg TASK_NO "${task_no}" '.tasks | .[$TASK_NO] | .startingPoint.mode')"

  #Determine if mode is internal-instance.
  if [[ "$MODE" == "internal-instance" ]]; then
    KUBECTL_ACCESS="$(echo "${task_json}" | jq -r --arg TASK_NO "${task_no}" '.tasks | .[$TASK_NO] | .startingPoint.kubectlAccess')"

    if [[ "$KUBECTL_ACCESS" == "false" ]]; then
      ssh "$INTERNAL_HOST_IP" '[[ $(compgen -d ~/.kube) ]] && mv ~/.kube /var/local/'
    else
      ssh "$INTERNAL_HOST_IP" '[[ $(compgen -d ~/.kube) ]] || mv /var/local/.kube ~/'
    fi

    ssh_internal

  #Determine if mode is node.
  elif [[ "$MODE" == "node" ]]; then
    NODE_TYPE="$(echo "${task_json}" | jq -r --arg TASK_NO "${task_no}" '.tasks | .[$TASK_NO] | .startingPoint.nodeId')"

    if [[ "$NODE_TYPE" =~ "master" ]]; then
      NODE_NUMBER="${NODE_TYPE#'master-'}"

      ssh_master "$NODE_NUMBER"

    elif [[ "$NODE_TYPE" =~ "node" ]]; then
      NODE_NUMBER="${NODE_TYPE#'node-'}"

      ssh_node "$NODE_NUMBER"
    else
      warning "An unrecognised node type has been selected. Please report this to the scenario author."
      info "Please use the ssh helper 'ssh_node ...' to start the task."
      exit 3
    fi

  #Determine if mode is pod.
  elif [[ "$MODE" == "pod" ]]; then
    POD_NAME="$(echo "${task_json}" | jq -r --arg TASK_NO "${task_no}" '.tasks | .[$TASK_NO] | .startingPoint.podName')"
    POD_NS="$(echo "${task_json}" | jq -r --arg TASK_NO "${task_no}" '.tasks | .[$TASK_NO] | .startingPoint.podNamespace')"
    CONTAINER_NAME="$(echo "${task_json}" | jq -r --arg TASK_NO "${task_no}" '.tasks | .[$TASK_NO] | .startingPoint.containerName')"

    if [[ ${CONTAINER_NAME} != "null" ]]; then
      # shellcheck disable=SC2029
      ssh -tt "$INTERNAL_HOST_IP" "kubectl exec -it -n ${POD_NS} -c ${CONTAINER_NAME} ${POD_NAME} -- bash"
    else
      # shellcheck disable=SC2029
      ssh -tt "$INTERNAL_HOST_IP" "kubectl exec -it -n ${POD_NS} ${POD_NAME} -- bash"
    fi

  #Determine if mode is attack.
  elif [[ "$MODE" == "attack" ]]; then
    echo "The starting point for this task is in this container. Please use the ssh helpers 'ssh_internal,' 'ssh_master' and 'ssh_node ...' to start the task."

  #Determine if mode is null.
  elif [[ "$MODE" == "null" ]]; then
    echo "No starting point has been configured for this task. Please use the ssh helpers 'ssh_internal,' 'ssh_master' and 'ssh_node ...' to start the task."

  else
    warning "An unrecognised starting point has been selected. Please report this to the scenario author."
    info "Please use the ssh helpers 'ssh_internal,' 'ssh_master' and 'ssh_node ...' to start the task."
    exit 4
  fi
}
readonly -f starting_point
export -f starting_point

#
# SSH helpers.
#
ssh_master() {
  local INDEX="${1:-}"

  if ! check_if_task_set; then
    return 1
  fi

  # shellcheck disable=SC2086
  IFS=', ' read -r -a MASTER_NODES <<<"${MASTER_IP_ADDRESSES}"
  export MASTER_NODES

  if [[ "${INDEX}" == "" ]]; then
    if [[ "${#MASTER_NODES[@]}" == 1 ]]; then
      INDEX=0
    else
      warning "Please supply a number"
      return 1
    fi
  fi

  if [[ "${INDEX}" -gt "${#MASTER_NODES[@]}" ]]; then
    warning "Master node '${INDEX}' does not exist"
    return 1
  fi

  ssh root@"${MASTER_NODES[${INDEX}]}"
}
readonly -f ssh_master
export -f ssh_master

ssh_node() {
  local INDEX="${1:-}"

  if ! check_if_task_set; then
    return 1
  fi

  IFS=', ' read -r -a WORKER_NODES <<<"${NODE_IP_ADDRESSES}"
  export WORKER_NODES

  if [[ "${INDEX}" == "" ]]; then
    if [[ "${#WORKER_NODES[@]}" == 1 ]]; then
      INDEX=0
    else
      warning "Please supply a node number"
      return 1
    fi
  fi

  if [[ "${INDEX}" -gt "${#WORKER_NODES[@]}" ]]; then
    warning "Worker node '${INDEX}' does not exist"
    return 1
  fi

  ssh root@"${WORKER_NODES[${INDEX}]}"
}
readonly -f ssh_node
export -f ssh_node

ssh_internal() {
  if ! check_if_task_set; then
    return 1
  fi

  ssh root@"${INTERNAL_HOST_IP}"
}
readonly -f ssh_internal
export -f ssh_internal

#
# Display info message
#
welcome() {
  IFS=', ' read -r -a MASTER_NODES <<<"${MASTER_IP_ADDRESSES}"
  IFS=', ' read -r -a WORKER_NODES <<<"${NODE_IP_ADDRESSES}"
  info ' '
  info "You have found a private kubernetes cluster."
  info ' '
  info "There are ${BOLD}${#MASTER_NODES[@]}${NORMAL} ${COLOUR_BLUE}master and ${BOLD}${#WORKER_NODES[@]}${NORMAL} ${COLOUR_BLUE}nodes in the cluster."
  info ' '
  info 'You can view the details of currently provisioned scenario and tasks with:'
  info "  \$ scenario"
  info ' '
  info "If this is your first scenario or you would like a refresher, you can view the helpers and commands for interacting with the scenario and tasks with:"
  info "  \$ list_commands"
  info ' '
  info "To see this message again:"
  info "  \$ welcome"
}
readonly -f welcome
export -f welcome

scenario() {
  if [[ -f /challenge.txt ]]; then
    envsubst </challenge.txt
    info ' '
    info "If this is your first scenario or you would like a refresher, you can view the helpers and commands for interacting with the scenario and tasks with:"
    info "  \$ list_commands"
    info ' '
    info "To see the welcome message again:"
    info "  \$ welcome"
    info ' '
    info "To see this message again:"
    info "  \$ scenario"
  else
    warning "No scenario instructions found. Have you launched a scenario with 'simulator scenario launch ...'?"
  fi
}
readonly -f scenario
export -f scenario

list_commands() {
  info 'There is a rudimentary beta scoring mechanism. You will be scored based'
  info 'on how many hints you have seen but it currently does not validate that'
  info 'you correctly completed the task. This is in the works :)'
  info ' '
  info 'Remember, when you start a task you will only have one opportunity to '
  info 'be scored on that task. Once it is ended or if you switch to another '
  info 'task, you will either be scored or marked as "skipping scoring". You '
  info 'can still return to the task but scoring will be fixed to the result of'
  info 'your first attempt.'
  info ' '
  info 'To start a task, type start_task followed by the task number, e.g:'
  info ' '
  info "  \$ start_task 1"
  info ' '
  info "Begin to work on the scenario from its starting point, from somewhere inside the infrastructure:"
  info ' '
  info "  \$ starting_point"
  info ' '
  info 'Getting stuck? See the next hint or recap'
  info ' '
  info "  \$ next_hint"
  info "  \$ show_hints"
  info ' '
  info 'To end a task, you can start a new task or:'
  info ' '
  info "  \$ end_task"
  info ' '
  info ' '
  info "SSH to master(s), node(s), and an internal node with network access to the cluster:"
  info ' '
  info "  \$ ssh_master 0"
  info "  \$ ssh_node 1"
  info "  \$ ssh_internal"
  info ' '
  info 'You can view the details of currently provisioned scenario and tasks with:'
  info "  \$ scenario"
  info ' '
  info "To see the welcome again:"
  info "  \$ welcome"
  info ' '
  info "To see this message again:"
  info "  \$ list_commands"
}
readonly -f list_commands
export -f list_commands

main
